package com.sf.osms.dep.lock;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.sql.Timestamp;
import java.util.Date;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.DbTimestampType;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.sf.novatar.base.dao.CommonDao;
import com.sf.novatar.cluster.IKeepAliveAndLockResolver;

@Component
@Transactional
public class KeepAliveDbResolver extends CommonDao implements IKeepAliveAndLockResolver{
	
	  private static String queryLockedSql = "select count(1) from TS_SYNC_LOCK where lock_code = ?0 and instance_id = ?1 and lock_state = 1 and LAST_ALIVE_TM is not null and last_alive_tm >= ?2";
	  private static String countLockExist = "select count(1) from TS_SYNC_LOCK where lock_code = ?0";
	  private static String insertLock = "insert into TS_SYNC_LOCK(lock_code, instance_id, last_alive_tm, lock_tm, lock_state) values (?0, ?1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1)";
	  private static String updateAlive = "update TS_SYNC_LOCK set last_alive_tm = CURRENT_TIMESTAMP where instance_id = ?0";
	  private static String updateLockSql = "update TS_SYNC_LOCK set instance_id = ?0, lock_state = 1, LAST_ALIVE_TM = CURRENT_TIMESTAMP, LOCK_TM = CURRENT_TIMESTAMP where lock_code = ?1 and (lock_state = 0 or last_alive_tm is null or last_alive_tm < ?2)";
	  private static String releaseLockSql = "update TS_SYNC_LOCK set instance_id = null, lock_state = 0, LAST_ALIVE_TM = null, LOCK_TM = null where lock_code = ?0 and instance_id = ?1 and lock_state = 1";
	  
	  public void updateAlive()
	  {
	    int rs = super.executeSQL(updateAlive, new Object[] {getInstanceId()});
	    if (rs > 0) {
	      this.logger.debug("Update alive for instance {}, effective records {}.", 
	        getInstanceId(), Integer.valueOf(rs));
	    }
	  }
	  
	  public boolean getLockFor(String lockCode)
	  {
	    try
	    {
	      Session session = getCurrentSession();
	      Query query = session.createSQLQuery(countLockExist);
	      query.setString("0", lockCode);
	      boolean exist = ((Number)query.uniqueResult()).intValue() > 0;
	      Date dbTime = DbTimestampType.INSTANCE.seed((SessionImplementor)session);
	      int rs = 0;
	      boolean locked = false;
	      if (!exist)
	      {
	        rs = super.executeSQL(insertLock, new Object[] { lockCode, getInstanceId() });
	        locked = rs == 1;
	      }
	      else if (queryLockFor(session, lockCode, dbTime))
	      {
	        locked = true;
	      }
	      else
	      {
	        rs = super.executeSQL(updateLockSql, new Object[] { getInstanceId(), lockCode, new Timestamp(dbTime
	          .getTime() - 120000L) });
	        locked = rs == 1;
	      }
	      this.logger.info("Try get lock of {} for instance {}, result {}.", new Object[] { lockCode, getInstanceId(), Boolean.valueOf(locked) });
	      return locked;
	    }
	    catch (HibernateException e)
	    {
	      this.logger.info("Try get lock of {} for instance {} exception.", lockCode, getInstanceId());
	      this.logger.error("Get sync lock exception.", e);
	    }
	    return false;
	  }
	  
	  @Transactional(readOnly=true)
	  public boolean isLockedFor(String lockCode)
	  {
	    try
	    {
	      Session session = getCurrentSession();
	      Date dbTime = DbTimestampType.INSTANCE.seed((SessionImplementor)session);
	      return queryLockFor(session, lockCode, dbTime);
	    }
	    catch (HibernateException e)
	    {
	      this.logger.error("Query sync lock exception.", e);
	    }
	    return false;
	  }
	  
	  private boolean queryLockFor(Session session, String lockCode, Date dbTime)
	  {
	    Query query = session.createSQLQuery(queryLockedSql);
	    query.setString("0", lockCode);
	    query.setString("1", getInstanceId());
	    query.setTimestamp("2", new Timestamp(dbTime.getTime() - 30000L));
	    
	    return ((Number)query.uniqueResult()).intValue() > 0;
	  }
	  
	  public void releaseLockFor(String lockCode)
	  {
	    super.executeSQL(releaseLockSql, new Object[] { lockCode, getInstanceId() });
	    this.logger.info("Release lock of {} for instance {}.", lockCode, getInstanceId());
	  }
	  
	  private String getInstanceId(){
		  RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
		  return runtime.getName();
		 
	  }
}
